#ifndef __SIGNWAY_ERR_H__
#define __SIGNWAY_ERR_H__

#ifdef __cplusplus
extern "C" {
#endif /**< _cplusplus */

typedef unsigned int uint32_t;
#define TAG __FILE__

/**
 * @brief Log level
 */
typedef enum {
    SIGNWAY_LOG_NONE,       /*!< No log output */
    SIGNWAY_LOG_ERROR,      /*!< Critical errors, software module can not recover on its own */
    SIGNWAY_LOG_WARN,       /*!< Error conditions from which recovery measures have been taken */
    SIGNWAY_LOG_INFO,       /*!< Information messages which describe normal flow of events */
    SIGNWAY_LOG_DEBUG,      /*!< Extra information which is not necessary for normal use (values, pointers, sizes, etc). */
    SIGNWAY_LOG_VERBOSE     /*!< Bigger chunks of debugging information, or frequent messages which can potentially flood the output. */
} signway_log_level_t;

typedef int signway_err_t;

/* Definitions for error constants. */

/* Definitions for error constants. */
#define SIGNWAY_OK                      0        /**< signway_err_t value indicating success (no error) */
#define SIGNWAY_FAIL                    -1       /**< Generic signway_err_t code indicating failure */

#define SIGNWAY_ERR_NO_MEM              0x100001 /**< Out of memory */
#define SIGNWAY_ERR_INVALID_ARG         0x100002 /**< Invalid argument */
#define SIGNWAY_ERR_INVALID_STATE       0x100003 /**< Invalid state */
#define SIGNWAY_ERR_INVALID_SIZE        0x100004 /**< Invalid size */
#define SIGNWAY_ERR_NOT_FOUND           0x100005 /**< Requested resource not found */
#define SIGNWAY_ERR_NOT_SUPPORTED       0x100006 /**< Operation or feature not supported */
#define SIGNWAY_ERR_TIMEOUT             0x100007 /**< Operation timed out */
#define SIGNWAY_ERR_INVALID_RESPONSE    0x100008 /**< Received response was invalid */
#define SIGNWAY_ERR_INVALID_CRC         0x100009 /**< CRC or checksum was invalid */
#define SIGNWAY_ERR_INVALID_VERSION     0x10000A /**< Version was invalid */
#define SIGNWAY_ERR_INVALID_MAC         0x10000B /**< MAC address was invalid */
#define SIGNWAY_ERR_NOT_INIT            0x10000C /**< MAC address was invalid */
#define SIGNWAY_ERR_BUF                 0x10000D /**< The buffer is too small */

#define SIGNWAY_ERR_MWIFI_BASE          0x200000 /**< Starting number of MWIFI error codes */
#define SIGNWAY_ERR_MESPNOW_BASE        0x300000 /**< Starting number of MESPNOW error codes */
#define SIGNWAY_ERR_MCONFIG_BASE        0x400000 /**< Starting number of MCONFIG error codes */
#define SIGNWAY_ERR_MUPGRADE_BASE       0x500000 /**< Starting number of MUPGRADE error codes */
#define SIGNWAY_ERR_MDEBUG_BASE         0x600000 /**< Starting number of MDEBUG error codes */
#define SIGNWAY_ERR_MLINK_BASE          0x700000 /**< Starting number of MLINK error codes */
#define SIGNWAY_ERR_CUSTOM_BASE         0x800000 /**< Starting number of COUSTOM error codes */

#define CONFIG_LOG_COLORS
#ifdef CONFIG_LOG_COLORS
#define LOG_COLOR_BLACK   "30"
#define LOG_COLOR_RED     "31"
#define LOG_COLOR_GREEN   "32"
#define LOG_COLOR_BROWN   "33"
#define LOG_COLOR_BLUE    "34"
#define LOG_COLOR_PURPLE  "35"
#define LOG_COLOR_CYAN    "36"
#define LOG_COLOR(COLOR)  "\033[0;" COLOR "m"
#define LOG_BOLD(COLOR)   "\033[1;" COLOR "m"
#define LOG_RESET_COLOR   "\033[0m"
#define LOG_COLOR_E       LOG_COLOR(LOG_COLOR_RED)
#define LOG_COLOR_W       LOG_COLOR(LOG_COLOR_BROWN)
#define LOG_COLOR_I       LOG_COLOR(LOG_COLOR_GREEN)
#define LOG_COLOR_D
#define LOG_COLOR_V
#else //CONFIG_LOG_COLORS
#define LOG_COLOR_E
#define LOG_COLOR_W
#define LOG_COLOR_I
#define LOG_COLOR_D
#define LOG_COLOR_V
#define LOG_RESET_COLOR
#endif //CONFIG_LOG_COLORS

/**
  * @brief Returns string for signway_err_t error codes
  *
  * This function finds the error code in a pre-generated lookup-table and
  * returns its string representation.
  *
  * The function is generated by the Python script
  * tools/gen_signway_err_to_name.py which should be run each time an signway_err_t
  * error is modified, created or removed from the IDF project.
  *
  * @param code signway_err_t error code
  * @return string error message
  */
const char *signway_err_to_name(signway_err_t code);

/**
 * @brief Write message into the log
 *
 * This function is not intended to be used directly. Instead, use one of
 * SIGNWAY_LOGE, SIGNWAY_LOGW, SIGNWAY_LOGI, SIGNWAY_LOGD, SIGNWAY_LOGV macros.
 *
 * This function or these macros should not be used from an interrupt.
 */
void signway_log_write(signway_log_level_t level, const char* tag, const char* format, ...) __attribute__ ((format (printf, 3, 4)));

/**
 * @return time stamp 时间戳
 */
uint32_t signway_log_timestamp(void);

#define CONFIG_SIGNWAY_LOG_LEVEL SIGNWAY_LOG_NONE
#ifndef CONFIG_SIGNWAY_LOG_LEVEL
#define CONFIG_SIGNWAY_LOG_LEVEL SIGNWAY_LOG_DEBUG
#endif /**< CONFIG_SIGNWAY_LOG_LEVEL */
#define SIGNWAY_LOG_LEVEL CONFIG_SIGNWAY_LOG_LEVEL

#define SIGNWAY_LOG_FORMAT(letter, format)  LOG_COLOR_ ## letter #letter " (%05d) [%s, %d]: " format LOG_RESET_COLOR "\n"

#define SIGNWAY_LOGE( format, ... ) do { \
        if (SIGNWAY_LOG_LEVEL >= SIGNWAY_LOG_ERROR) { \
            signway_log_write(SIGNWAY_LOG_ERROR, TAG, SIGNWAY_LOG_FORMAT(E, format), signway_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define SIGNWAY_LOGW( format, ... ) do { \
        if (SIGNWAY_LOG_LEVEL >= SIGNWAY_LOG_WARN) { \
            signway_log_write(SIGNWAY_LOG_WARN, TAG, SIGNWAY_LOG_FORMAT(W, format), signway_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define SIGNWAY_LOGI( format, ... ) do { \
        if (SIGNWAY_LOG_LEVEL >= SIGNWAY_LOG_INFO) { \
            signway_log_write(SIGNWAY_LOG_INFO, TAG, SIGNWAY_LOG_FORMAT(I, format), signway_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define SIGNWAY_LOGD( format, ... ) do { \
        if (SIGNWAY_LOG_LEVEL >= SIGNWAY_LOG_DEBUG) { \
            signway_log_write(SIGNWAY_LOG_DEBUG, TAG, SIGNWAY_LOG_FORMAT(D, format), signway_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define SIGNWAY_LOGV( format, ... ) do { \
        if (SIGNWAY_LOG_LEVEL >= SIGNWAY_LOG_VERBOSE) { \
            signway_log_write(SIGNWAY_LOG_VERBOSE, TAG, SIGNWAY_LOG_FORMAT(V, format), signway_log_timestamp(), TAG, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define SIGNWAY_ARRAY_LOGI( format, array, len) do { \
        int _len = (len); \
        for (size_t i = 0; i < _len; i++) { \
            signway_log_write(SIGNWAY_LOG_VERBOSE, TAG, format, array[i]); \
            if ((i != 0) && (i % 10 == 0)) { \
                signway_log_write(SIGNWAY_LOG_VERBOSE, TAG, "\n"); \
            } \
        } \
        signway_log_write(SIGNWAY_LOG_VERBOSE, TAG, "\n"); \
    } while (0)

/**
 * Macro which can be used to check the error code,
 * and terminate the program in case the code is not SIGNWAY_OK.
 * Prints the error code, error location, and the failed statement to serial output.
 *
 * Disabled if assertions are disabled.
 */
#define SIGNWAY_ERROR_CHECK(con, err, format, ...) do { \
        if (con) { \
            if(*format != '\0') \
                SIGNWAY_LOGW("<%s> " format, signway_err_to_name(err), ##__VA_ARGS__); \
            return err; \
        } \
    } while(0)

/**
 * @brief Macro serves similar purpose as ``assert``, except that it checks `signway_err_t`
 *        value rather than a `bool` condition. If the argument of `SIGNWAY_ERROR_ASSERT`
 *        is not equal `SIGNWAY_OK`, then an error message is printed on the console,
 *         and `abort()` is called.
 *
 * @note If `IDF monitor` is used, addresses in the backtrace will be converted
 *       to file names and line numbers.
 *
 * @param  err [description]
 * @return     [description]
 */
#define SIGNWAY_ERROR_ASSERT(err) do { \
        signway_err_t __err_rc = (err); \
        if (__err_rc != SIGNWAY_OK) { \
            SIGNWAY_LOGW("<%s> SIGNWAY_ERROR_ASSERT failed, at 0x%08x, expression: %s", \
                     signway_err_to_name(__err_rc), (intptr_t)__builtin_return_address(0) - 3, __ASSERT_FUNC); \
            assert(0 && #err); \
        } \
    } while(0)

#define SIGNWAY_ERROR_GOTO(con, lable, format, ...) do { \
        if (con) { \
            if(*format != '\0') \
                SIGNWAY_LOGW(format, ##__VA_ARGS__); \
            goto lable; \
        } \
    } while(0)

#define SIGNWAY_ERROR_CONTINUE(con, format, ...) { \
        if (con) { \
            if(*format != '\0') \
                SIGNWAY_LOGW(format, ##__VA_ARGS__); \
            continue; \
        } \
    }

#define SIGNWAY_ERROR_BREAK(con, format, ...) { \
        if (con) { \
            if(*format != '\0') \
                SIGNWAY_LOGW(format, ##__VA_ARGS__); \
            break; \
        } \
    }

#define SIGNWAY_PARAM_CHECK(con) do { \
        if (!(con)) { \
            SIGNWAY_LOGE("<SIGNWAY_ERR_INVALID_ARG> !(%s)", #con); \
            return SIGNWAY_ERR_INVALID_ARG; \
        } \
    } while(0)

#ifdef __cplusplus
}
#endif /**< _cplusplus */
#endif /**< __SIGNWAY_ERR_H__ */
